package com.hisense.ovcloud.mqtt.business.service;


import cn.hutool.json.JSONUtil;
import com.hisense.ovcloud.commons.configs.redis.RedisDao;
import com.hisense.ovcloud.commons.dto.common.AesKeyInfoInCache;
import com.hisense.ovcloud.commons.dto.common.DeviceEndpointName;
import com.hisense.ovcloud.commons.dto.common.WifiListSingleMap;
import com.hisense.ovcloud.commons.dto.common.WifiOnlineInRedisDto;
import com.hisense.ovcloud.commons.dto.common.mqtt.HookMessage;
import com.hisense.ovcloud.commons.dto.req.*;
import com.hisense.ovcloud.commons.dto.resp.GetDeviceInfoResp;
import com.hisense.ovcloud.mqtt.business.config.Constants;
import com.hisense.ovcloud.mqtt.business.dto.resp.EMQXDeviceStatusResp;
import io.vertx.core.buffer.Buffer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.hisense.ovcloud.mqtt.business.config.Constants.EMQX_REST_URL;

@Service
@Slf4j
public class AuthService implements InitializingBean {
    private final RedisDao redisDao;
    public final WebClient webClient;

    private final MQTTClientConnector mqttClientConnector;

    public AuthService(@Qualifier("base") WebClient webClient, RedisDao redisDao, MQTTClientConnector mqttClientConnector) {
        this.webClient = webClient;
        this.redisDao = redisDao;
        this.mqttClientConnector = mqttClientConnector;
    }

    @Value("${emqx.api.username:admin}")
    private String username;

    @Value("${emqx.api.password:public}")
    private String password;

    private static String emqxAPIAuthHeader = "Basic ";



    public Mono<ServerResponse> handleGetHook(ServerRequest req){
        return req.bodyToMono(String.class)
                .flatMap(r -> Mono.just(JSONUtil.toBean(messageFiltering(r), HookMessage.class))
                        .onErrorReturn(HookMessage.builder().build())
                        .zipWhen(hookMessage ->
                                    Mono.just(hookMessage.getAction().contains("session_terminated"))
                                            .zipWhen(res -> {
                                                if(StringUtils.isNotEmpty(hookMessage.getUsername())){
                                                    String domain = hookMessage.getUsername().split("/")[0];
                                                    String subDomain = hookMessage.getUsername().split("/")[1];
                                                    String wifiId = hookMessage.getUsername().split("/")[2];
                                                    if(StringUtils.isNotEmpty(domain) &&
                                                            StringUtils.isNotEmpty(subDomain) &&
                                                            StringUtils.isNotEmpty(wifiId) &&
                                                            !domain.equals("0") &&
                                                            !subDomain.equals("0")
                                                    ){
                                                        return mqttClientConnector.notifyWifiOnlineToHitachi(
                                                                        Mono.just(NotifyWifiOnlineToHitachiReq.builder()
                                                                                .majorDomainId(Integer.parseInt(domain))
                                                                                .subDomainId(Integer.parseInt(subDomain))
                                                                                .physicalId(wifiId)
                                                                                .payload(Constants.STATUS_OFFLINE_PAYLOAD)
                                                                                .build()))
                                                                        .zipWhen(tu -> ServerResponse.ok().body(Mono.just(true), Boolean.class)
                                                                ,(x, y) -> y);
                                                    }else{
                                                        return ServerResponse.ok().body(Mono.just(false), Boolean.class);
                                                    }
                                                }else{
                                                    return ServerResponse.ok().body(Mono.just(false), Boolean.class);
                                                }

                                            }, (x, y) -> y)
                                , (x, y) -> y));
    }

    public Mono<ServerResponse> handlenNotifyWifiOnlineStatusToHitachi(ServerRequest req){
        return req.bodyToMono(NotifyWifiOnlineToHitachiReq.class)
                .flatMap(r -> mqttClientConnector.notifyWifiOnlineToHitachi(
                        Mono.just(r))
                ).zipWhen(tu -> ServerResponse.ok().body(Mono.just(true), Boolean.class)
                        ,(x, y) -> y);
    }

    public Mono<ServerResponse> handleGetWifiStats(ServerRequest req){
        return req.bodyToMono(GetMqttWifiStatsReq.class)
                .flatMap(r ->  ServerResponse.ok().body(getWifiStats(r.getWifiId()), GetDeviceInfoResp.class));
    }

    public Mono<ServerResponse> handleSetCmd(ServerRequest req){
        return req.bodyToMono(MqttThirdPartySetCmdReq.class)
                .flatMap(r ->  ServerResponse.ok().body(setCmd(Mono.just(r)), Boolean.class));
    }

    public Mono<ServerResponse> handleGetSecretKey(ServerRequest req){
        return req.bodyToMono(GetAesKeySecretReq.class)
                .flatMap(r -> mqttClientConnector.fetchAesKeyInfoInCaches(r.getDomain())
                        .zipWhen(keyInfo -> {
                            if(keyInfo.getAccess_key().equals(r.getAesKey())){
                                return ServerResponse.ok().body(Mono.just(keyInfo), AesKeyInfoInCache.class);
                            }else{
                                return ServerResponse.ok().body(Mono.just(AesKeyInfoInCache.builder().build()), AesKeyInfoInCache.class);
                            }
                        }, (x, y) -> y));
    }

    public Mono<Boolean> setCmd(Mono<MqttThirdPartySetCmdReq> req){
        return req.flatMap(r -> Mono.just(mqttClientConnector.send(String.format("%d/%d/%s/queueMode/set/", r.getMajorDomainId(), r.getSubDomainId(), r.getPhysicalId()),
                Buffer.buffer(java.util.Base64.getDecoder().decode(r.getPayload().getBytes(StandardCharsets.UTF_8))))).zipWhen(d -> Mono.just(true), (x, y) -> y)
        );
    }

    public Mono<WifiOnlineInRedisDto> getWifiOnlineInRedisDto(String wifiId){
        return redisDao.get(String.format("%s%s", Constants.ONLINE_REDIS_PREFIX, wifiId))
                .switchIfEmpty(Mono.just(Constants.REDIS_NULL_VALUE))
                .zipWhen(res -> {
                    if(res.equals(Constants.REDIS_NULL_VALUE)){
                        return Mono.just(WifiOnlineInRedisDto.builder().build());
                    }else{
                        return Mono.just(JSONUtil.toBean(res, WifiOnlineInRedisDto.class));
                    }
                }, (x, y) ->y);
    }

    public Mono<GetDeviceInfoResp> getWifiStats(String wifiId){
        return redisDao.get(String.format("%s%s", Constants.ONLINE_REDIS_PREFIX, wifiId))
                .switchIfEmpty(Mono.just(Constants.REDIS_NULL_VALUE))
                .zipWhen(res -> {
                    if(res.equals(Constants.REDIS_NULL_VALUE)) {
                        return Mono.just(GetDeviceInfoResp.builder().onlineStatus(Constants.STATUS_OFFLINE).build());
                    } else {
                        log.info("res from redis: {} for wifiId {}", res, wifiId);
                        WifiOnlineInRedisDto dto = JSONUtil.toBean(res, WifiOnlineInRedisDto.class);
                        if(StringUtils.isNotEmpty(dto.getClientId())){
                            return webClient.get()
                                    .uri(EMQX_REST_URL + dto.getClientId())
                                    .header("Authorization", emqxAPIAuthHeader)
                                    .retrieve()
                                    .bodyToMono(EMQXDeviceStatusResp.class)
                                    .doOnSuccess(res1 -> {
                                        log.info("succeed to fetch online status from emqx for wifiId {}", wifiId);
                                    })
                                    .doOnError(err -> log.error("failed to fetch online status from emqx for wifiId {}", wifiId, err))
                                    .zipWhen(s -> Mono.just(GetDeviceInfoResp.builder().onlineStatus(s.getData().getFirst().isConnected() ? Constants.STATUS_ONLINE : Constants.STATUS_OFFLINE).build()), (x, y) -> y);
                        }else{
                            return Mono.just(GetDeviceInfoResp.builder().onlineStatus(Constants.STATUS_OFFLINE).build());
                        }
                    }
                }, (x, y) -> y);
    }


    public String messageFiltering(String oldMsg){
        return oldMsg.replaceAll("%2B", "+")
                .replaceAll("%20", " ")
                .replaceAll("%2F", "/")
                .replaceAll("%3F", "?")
                .replaceAll("%23", "#")
                .replaceAll("%25", "%")
                .replaceAll("%26", "&")
                .replaceAll("%3D", "=");

    }

    public static String calcutePassword(String clientId, String timestamp, String preKey, String payload){
        return HmacUtils.hmacMd5Hex(preKey.getBytes(StandardCharsets.UTF_8), String.format("clientId%stimestamp%s", clientId, timestamp).getBytes(StandardCharsets.UTF_8));
    }

    public static void main(String[] args) {
        Pattern p = Pattern.compile(Constants.ENDPOINT_NAME_PATTHEN);
        Matcher m = p.matcher("9/24/30020b04b0cda838_1_1_1720766085");
        String payload = "MDSVpWtmAAAAAEEBAgEACAIJAAFlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgECAQCAkAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIAAAAAAAAAAAAAAAAAQAIAgkAAmMAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAgAABAAAAAAAGRUgHwAEIAECIAAAAAAAAAAAAAAAAAAAAAAVGwAAAAAAAAAAAAABAAgCCQADRwAAAAAVwhQWGwAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdIw=";
        DeviceEndpointName deviceEndpointName = DeviceEndpointName.builder().rawName("").build();
        if (m.find()) {
            int totalParamCount = m.groupCount();
            List<String> allParameters = new ArrayList<>();

            for (int i = 0; i < totalParamCount + 1; i++) {
                allParameters.add(m.group(i));
            }

            deviceEndpointName.setMajorDomainID(Long.parseLong(allParameters.get(1)));
            deviceEndpointName.setSubDomainID(Long.parseLong(allParameters.get(2)));
            deviceEndpointName.setPhysicalID(allParameters.get(3));
            deviceEndpointName.setSecureMode(allParameters.get(4));
            deviceEndpointName.setSignMethod(allParameters.get(5));
            deviceEndpointName.setTimestamp(allParameters.get(6));

            System.out.println(calcutePassword("2/19/30028812b0cda838", "1732175037", "1c38422bcefb33b9a2e4458506832912", payload));
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        emqxAPIAuthHeader = emqxAPIAuthHeader + Base64.getEncoder()
                .encodeToString((username + ":" + password).getBytes());;
    }
}









































